---
title: AI
description: AI 驱动的写作辅助。
docs:
  - route: https://pro.platejs.org/docs/examples/ai
    title: Plus
---

<ComponentPreview name="ai-demo" />

<PackageInfo>

## 功能特性

- **上下文感知的命令菜单**,可适应光标、文本选择和块选择工作流。
- **流式 Markdown/MDX 插入**,支持表格、列和代码块,由 `streamInsertChunk` 提供支持。
- **插入和对话审查模式**,通过 `withAIBatch` 和 `tf.ai.undo()` 实现撤销安全的批处理。
- **块选择感知的变换**,使用 `tf.aiChat.replaceSelection` 和 `tf.aiChat.insertBelow` 替换或追加整个部分。
- **直接集成 `@ai-sdk/react`**,使 `api.aiChat.submit` 可以从 Vercel AI SDK 助手流式传输响应。
- **建议和评论工具**,可对比 AI 编辑、接受/拒绝更改,并将 AI 反馈映射回文档范围。

</PackageInfo>

## Kit 用法

<Steps>

### 安装

添加 AI 功能最快的方法是使用 `AIKit`。它包含配置好的 `AIPlugin`、`AIChatPlugin`、Markdown 流式传输助手、光标覆盖层及其 [Plate UI](/docs/installation/plate-ui) 组件。

<ComponentSource name="ai-kit" />

- [`AIMenu`](/docs/components/ai-menu):用于提示、工具快捷方式和对话审查的浮动命令界面。
- [`AILoadingBar`](/docs/components/ai-loading-bar):在编辑器容器中显示流式传输状态。
- [`AIAnchorElement`](/docs/components/ai-anchor-element):在流式传输期间用于定位浮动菜单的不可见锚点节点。
- [`AILeaf`](/docs/components/ai-leaf):使用微妙样式渲染 AI 标记的文本。

### 添加 Kit

```tsx
import { createPlateEditor } from 'platejs/react';
import { AIKit } from '@/components/editor/plugins/ai-kit';

const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...AIKit,
  ],
});
```

### 添加 API 路由

公开一个流式命令端点,代理你的模型提供商:

<ComponentSource name="ai-api" />

### 配置环境

在本地设置提供商凭证:

```bash title=".env.local"
AI_GATEWAY_API_KEY="your-api-key"
```

</Steps>

## 手动使用

<Steps>

### 安装

```bash
npm install @platejs/ai @platejs/markdown @platejs/selection @ai-sdk/react ai
```

`@platejs/suggestion` 是可选的,但对于基于差异的编辑建议是必需的。

### 添加插件

```tsx
import { createPlateEditor } from 'platejs/react';
import { AIChatPlugin, AIPlugin } from '@platejs/ai/react';
import { BlockSelectionPlugin } from '@platejs/selection/react';
import { MarkdownPlugin } from '@platejs/markdown';

export const editor = createPlateEditor({
  plugins: [
    BlockSelectionPlugin,
    MarkdownPlugin,
    AIPlugin,
    AIChatPlugin, // 在下一步中扩展
  ],
});
```

- `BlockSelectionPlugin`:启用多块选择,`AIChatPlugin` 依赖它来进行插入/替换变换。
- `MarkdownPlugin`:提供流式工具使用的 Markdown 序列化。
- `AIPlugin`:添加 AI 标记和用于撤销 AI 批处理的变换。
- `AIChatPlugin`:提供 AI 组合框、API 助手和变换。

使用 `AIPlugin.withComponent` 配合你自己的元素(或 [`AILeaf`](/docs/components/ai-leaf))来高亮显示 AI 生成的文本。

### 配置 AIChatPlugin

扩展 `AIChatPlugin` 以挂接流式传输和编辑。该示例镜像了 `AIKit` 的核心逻辑,同时保持 UI 无头化。

```tsx
import { AIChatPlugin, applyAISuggestions, streamInsertChunk, useChatChunk } from '@platejs/ai/react';
import { withAIBatch } from '@platejs/ai';
import { getPluginType, KEYS, PathApi } from 'platejs';
import { usePluginOption } from 'platejs/react';

export const aiChatPlugin = AIChatPlugin.extend({
  options: {
    chatOptions: {
      api: '/api/ai/command',
      body: {
        model: 'openai/gpt-4o-mini',
      },
    },
    trigger: ' ',
    triggerPreviousCharPattern: /^\s?$/,
  },
  useHooks: ({ editor, getOption }) => {
    const mode = usePluginOption(AIChatPlugin, 'mode');
    const toolName = usePluginOption(AIChatPlugin, 'toolName');

    useChatChunk({
      onChunk: ({ chunk, isFirst, text }) => {
        if (mode === 'insert') {
          if (isFirst) {
            editor.setOption(AIChatPlugin, 'streaming', true);

            editor.tf.insertNodes(
              {
                children: [{ text: '' }],
                type: getPluginType(editor, KEYS.aiChat),
              },
              {
                at: PathApi.next(editor.selection!.focus.path.slice(0, 1)),
              }
            );
          }

          if (!getOption('streaming')) return;

          withAIBatch(
            editor,
            () => {
              streamInsertChunk(editor, chunk, {
                textProps: {
                  [getPluginType(editor, KEYS.ai)]: true,
                },
              });
            },
            { split: isFirst }
          );
        }

        if (toolName === 'edit' && mode === 'chat') {
          withAIBatch(
            editor,
            () => {
              applyAISuggestions(editor, text);
            },
            { split: isFirst }
          );
        }
      },
      onFinish: () => {
        editor.setOption(AIChatPlugin, 'streaming', false);
        editor.setOption(AIChatPlugin, '_blockChunks', '');
        editor.setOption(AIChatPlugin, '_blockPath', null);
        editor.setOption(AIChatPlugin, '_mdxName', null);
      },
    });
  },
});
```

- `useChatChunk`:监视 `UseChatHelpers` 状态并生成增量块。
- `streamInsertChunk`:将 Markdown/MDX 流式传输到文档中,尽可能重用现有块。
- `applyAISuggestions`:当 `toolName === 'edit'` 时,将响应转换为临时建议节点。
- `withAIBatch`:标记历史批处理,使 `tf.ai.undo()` 仅撤销最后一次 AI 生成的更改。

当你扩展插件时,提供你自己的 `render` 组件(工具栏按钮、浮动菜单等)。

### 构建 API 路由

在服务器上处理 `api.aiChat.submit` 请求。每个请求都包含来自 `@ai-sdk/react` 的聊天 `messages` 和一个 `ctx` 负载,其中包含编辑器 `children`、当前 `selection` 和最后的 `toolName`。
[完整 API 示例](https://github.com/udecode/plate-playground-template/blob/main/src/app/api/ai/command/route.ts)

```ts title="app/api/ai/command/route.ts"
import { createGateway } from '@ai-sdk/gateway';
import { convertToCoreMessages, streamText } from 'ai';
import { createSlateEditor } from 'platejs';

import { BaseEditorKit } from '@/registry/components/editor/editor-base-kit';
import { markdownJoinerTransform } from '@/registry/lib/markdown-joiner-transform';

export async function POST(req: Request) {
  const { apiKey, ctx, messages, model } = await req.json();

  const editor = createSlateEditor({
    plugins: BaseEditorKit,
    selection: ctx.selection,
    value: ctx.children,
  });

  const gateway = createGateway({
    apiKey: apiKey ?? process.env.AI_GATEWAY_API_KEY!,
  });

  const result = streamText({
    experimental_transform: markdownJoinerTransform(),
    messages: convertToCoreMessages(messages),
    model: gateway(model ?? 'openai/gpt-4o-mini'),
    system: ctx.toolName === 'edit' ? 'You are an editor that rewrites user text.' : undefined,
  });

  return result.toDataStreamResponse();
}
```

- `ctx.children` 和 `ctx.selection` 被重新水合到 Slate 编辑器中,以便你可以构建丰富的提示(参见[提示模板](#提示模板))。
- 通过 `chatOptions.body` 转发提供商设置(模型、apiKey、温度、网关标志等);你添加的所有内容都会在 JSON 负载中原样传递,后端可以在调用 `createGateway` 前读取。
- 始终从服务器读取密钥。客户端应仅发送不透明标识符或短期令牌。
- 返回流式响应,以便 `useChat` 和 `useChatChunk` 可以逐步处理令牌。

### 连接 `useChat`

使用 `@ai-sdk/react` 桥接编辑器和模型端点。在插件上存储助手,以便变换可以重新加载、停止或显示聊天状态。

```tsx
import { useEffect } from 'react';

import { type UIMessage, DefaultChatTransport } from 'ai';
import { type UseChatHelpers, useChat } from '@ai-sdk/react';
import { AIChatPlugin } from '@platejs/ai/react';
import { useEditorPlugin } from 'platejs/react';

type ChatMessage = UIMessage<{}, { toolName: 'comment' | 'edit' | 'generate'; comment?: unknown }>;

export const useEditorAIChat = () => {
  const { editor, setOption } = useEditorPlugin(AIChatPlugin);

  const chat = useChat<ChatMessage>({
    id: 'editor',
    api: '/api/ai/command',
    transport: new DefaultChatTransport(),
    onData(data) {
      if (data.type === 'data-toolName') {
        editor.setOption(AIChatPlugin, 'toolName', data.data);
      }
    },
  });

  useEffect(() => {
    setOption('chat', chat as UseChatHelpers<ChatMessage>);
  }, [chat, setOption]);

  return chat;
};
```

将助手与 `useEditorChat` 结合使用以保持浮动菜单正确锚定:

```tsx
import { useEditorChat } from '@platejs/ai/react';

useEditorChat({
  chat,
  onOpenChange: (open) => {
    if (!open) chat.stop?.();
  },
});
```

现在你可以以编程方式提交提示:

```tsx
import { AIChatPlugin } from '@platejs/ai/react';

editor.getApi(AIChatPlugin).aiChat.submit('', {
  prompt: {
    default: 'Continue the document after {block}',
    selecting: 'Rewrite {selection} with a clearer tone',
  },
  toolName: 'generate',
});
```

</Steps>

## 提示模板

### 客户端提示

- `api.aiChat.submit` 接受一个 `EditorPrompt`。提供一个字符串、一个包含 `default`/`selecting`/`blockSelecting` 的对象,或一个接收 `{ editor, isSelecting, isBlockSelecting }` 的函数。客户端中的 `getEditorPrompt` 助手将该值转换为最终字符串。
- 将其与 `replacePlaceholders(editor, template, { prompt })` 结合使用,使用 `@platejs/ai` 生成的 Markdown 展开 `{editor}`、`{block}`、`{blockSelection}` 和 `{prompt}`。

```tsx
import { replacePlaceholders } from '@platejs/ai';

editor.getApi(AIChatPlugin).aiChat.submit('Improve tone', {
  prompt: ({ isSelecting }) =>
    isSelecting
      ? replacePlaceholders(editor, 'Rewrite {blockSelection} using a friendly tone.')
      : replacePlaceholders(editor, 'Continue {block} with two more sentences.'),
  toolName: 'generate',
});
```

### 服务器端提示

`apps/www/src/app/api/ai/command` 中的演示后端从 `ctx` 重构编辑器并构建结构化提示:

- `getChooseToolPrompt` 决定请求是 `generate`、`edit` 还是 `comment`。
- `getGeneratePrompt`、`getEditPrompt` 和 `getCommentPrompt` 将当前编辑器状态转换为针对每种模式量身定制的指令。
- 像 `getMarkdown`、`getMarkdownWithSelection` 和 `buildStructuredPrompt` 这样的实用助手(参见 `apps/www/src/app/api/ai/command/prompts.ts`)使得将块 ID、选择和 MDX 标签嵌入到 LLM 请求中变得容易。

增强从客户端发送的负载以微调服务器提示:

```ts
editor.setOption(aiChatPlugin, 'chatOptions', {
  api: '/api/ai/command',
  body: {
    model: 'openai/gpt-4o-mini',
    tone: 'playful',
    temperature: 0.4,
  },
});
```

`chatOptions.body` 下的所有内容都会到达路由处理程序,让你可以交换提供商、传递用户特定的元数据或分支到不同的提示模板。

## 键盘快捷键

<KeyTable>
  <KeyTableItem hotkey="Space">在空块中打开 AI 菜单(光标模式)</KeyTableItem>
  <KeyTableItem hotkey="Cmd + J">显示 AI 菜单(通过 `shortcuts.show` 设置)</KeyTableItem>
  <KeyTableItem hotkey="Escape">隐藏 AI 菜单并停止流式传输</KeyTableItem>
</KeyTable>

## 流式传输

流式传输工具在响应到达时保持复杂布局完整:

- `streamInsertChunk(editor, chunk, options)` 反序列化 Markdown 块,就地更新当前块,并根据需要追加新块。使用 `textProps`/`elementProps` 标记流式节点(例如,标记 AI 文本)。
- `streamDeserializeMd` 和 `streamDeserializeInlineMd` 提供较低级别的访问,如果你需要控制自定义节点类型的流式传输。
- `streamSerializeMd` 镜像编辑器状态,以便你可以检测流式内容和响应缓冲区之间的偏差。

当流式传输完成时,重置内部的 `_blockChunks`、`_blockPath` 和 `_mdxName` 选项,以便从干净的状态开始下一个响应。

## 流式传输示例

<ComponentPreview name="markdown-streaming-demo" />

## Plate Plus

<ComponentPreviewPro name="ai-pro" />

## 钩子

### `useAIChatEditor`

为聊天预览注册一个辅助编辑器,并使用块级记忆化反序列化 Markdown。

<API name="useAIChatEditor">
<APIParameters>
  <APIItem name="editor" type="SlateEditor">专用于聊天预览的编辑器实例。</APIItem>
  <APIItem name="content" type="string">模型返回的 Markdown 内容。</APIItem>
  <APIItem name="options" type="DeserializeMdOptions" optional>传递 `parser` 以在反序列化之前过滤标记。</APIItem>
</APIParameters>
</API>

```tsx
import { usePlateEditor } from 'platejs/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { AIChatPlugin, useAIChatEditor } from '@platejs/ai/react';

const aiPreviewEditor = usePlateEditor({
  plugins: [MarkdownPlugin, AIChatPlugin],
});

useAIChatEditor(aiPreviewEditor, responseMarkdown, {
  parser: { exclude: ['space'] },
});
```

### `useEditorChat`

将 `UseChatHelpers` 连接到编辑器状态,以便 AI 菜单知道是锚定到光标、选择还是块选择。

<API name="useEditorChat">
<APIParameters>
  <APIItem name="chat" type="UseChatHelpers&lt;ChatMessage&gt;">由 `useChat` 返回的助手。</APIItem>
  <APIItem name="onOpenBlockSelection" type="(blocks: NodeEntry[]) => void" optional>当菜单在块选择上打开时调用。</APIItem>
  <APIItem name="onOpenChange" type="(open: boolean) => void" optional>每当菜单打开或关闭时调用。</APIItem>
  <APIItem name="onOpenCursor" type="() => void" optional>当菜单在光标处打开时调用。</APIItem>
  <APIItem name="onOpenSelection" type="() => void" optional>当菜单在文本选择上打开时调用。</APIItem>
</APIParameters>
</API>

### `useChatChunk`

逐块流式传输聊天响应,并让你完全控制插入。

<API name="useChatChunk">
<APIParameters>
  <APIItem name="onChunk" type="(chunk: { chunk: string; isFirst: boolean; nodes: TText[]; text: string }) => void">处理每个流式块。</APIItem>
  <APIItem name="onFinish" type="({ content }: { content: string }) => void" optional>当流式传输完成时调用。</APIItem>
</APIParameters>
</API>

## 工具函数

### `withAIBatch`

将编辑器操作分组到单个历史批处理中,并将其标记为 AI 生成,以便 `tf.ai.undo()` 可以安全地删除它。

<API name="withAIBatch">
<APIParameters>
  <APIItem name="editor" type="SlateEditor">目标编辑器。</APIItem>
  <APIItem name="fn" type="() => void">要运行的操作。</APIItem>
  <APIItem name="options" type="{ split?: boolean }" optional>设置 `split: true` 以开始新的历史批处理。</APIItem>
</APIParameters>
</API>

### `applyAISuggestions`

将 AI 输出与存储的 `chatNodes` 进行差异比较,并写入临时建议节点。需要 `@platejs/suggestion`。

<API name="applyAISuggestions">
<APIParameters>
  <APIItem name="editor" type="SlateEditor">要应用建议的编辑器。</APIItem>
  <APIItem name="content" type="string">来自模型的 Markdown 响应。</APIItem>
</APIParameters>
</API>

补充助手允许你完成或丢弃差异:

- `acceptAISuggestions(editor)`:将临时建议节点转换为永久建议。
- `rejectAISuggestions(editor)`:删除临时建议节点并清除建议标记。

### `aiCommentToRange`

将流式评论元数据映射回文档范围,以便可以自动插入评论。

<API name="aiCommentToRange">
<APIParameters>
  <APIItem name="editor" type="PlateEditor">编辑器实例。</APIItem>
  <APIItem name="options" type="{ blockId: string; comment: string; content: string }">用于定位范围的块 ID 和文本。</APIItem>
</APIParameters>
<APIReturns type="{ start: BasePoint; end: BasePoint } | null">与评论匹配的范围,如果找不到则为 `null`。</APIReturns>
</API>

### `findTextRangeInBlock`

使用 LCS 在块内查找最接近匹配的模糊搜索助手。

<API name="findTextRangeInBlock">
<APIParameters>
  <APIItem name="node" type="TNode">要搜索的块节点。</APIItem>
  <APIItem name="searchText" type="string">要定位的文本片段。</APIItem>
</APIParameters>
<APIReturns type="{ start: { path: Path; offset: number }; end: { path: Path; offset: number } } | null">匹配的范围或 `null`。</APIReturns>
</API>

### `getEditorPrompt`

生成尊重光标、选择或块选择状态的提示。

<API name="getEditorPrompt">
<APIParameters>
  <APIItem name="editor" type="SlateEditor">提供上下文的编辑器。</APIItem>
  <APIItem name="options" type="{ prompt?: EditorPrompt }">描述提示的字符串、配置或函数。</APIItem>
</APIParameters>
<APIReturns type="string">上下文化的提示字符串。</APIReturns>
</API>

### `replacePlaceholders`

用序列化的 Markdown 替换像 `{editor}`、`{blockSelection}` 和 `{prompt}` 这样的占位符。

<API name="replacePlaceholders">
<APIParameters>
  <APIItem name="editor" type="SlateEditor">提供内容的编辑器。</APIItem>
  <APIItem name="text" type="string">模板文本。</APIItem>
  <APIItem name="options" type="{ prompt?: string }" optional>注入到 `{prompt}` 中的提示值。</APIItem>
</APIParameters>
<APIReturns type="string">占位符被 Markdown 替换后的模板。</APIReturns>
</API>

## 插件

### `AIPlugin`

向流式文本添加 `ai` 标记,并公开用于删除 AI 节点或撤销最后一个 AI 批处理的变换。使用 `.withComponent` 来使用自定义组件渲染 AI 标记的文本。

<API name="AIPlugin">
  <APIOptions>
    <APIItem name="node.isLeaf" type="true">AI 内容存储在文本节点上。</APIItem>
    <APIItem name="node.isDecoration" type="false">AI 标记是常规文本属性,而不是装饰。</APIItem>
  </APIOptions>
</API>

### `AIChatPlugin`

支持 AI 菜单、聊天状态和变换的主要插件。

<API name="AIChatPlugin">
  <APIOptions>
    <APIItem name="trigger" type="RegExp | string | string[]" optional>打开命令菜单的字符。默认为 `' '`。</APIItem>
    <APIItem name="triggerPreviousCharPattern" type="RegExp" optional>必须与触发器之前的字符匹配的模式。默认为 `/^\s?$/`。</APIItem>
    <APIItem name="triggerQuery" type="(editor: SlateEditor) => boolean" optional>返回 `false` 以取消在特定上下文中打开。</APIItem>
    <APIItem name="chat" type="UseChatHelpers&lt;ChatMessage&gt;" optional>存储来自 `useChat` 的助手,以便 API 调用可以访问它们。</APIItem>
    <APIItem name="chatNodes" type="TIdElement[]" optional>用于差异编辑建议的节点快照(内部管理)。</APIItem>
    <APIItem name="chatSelection" type="TRange | null" optional>提交提示之前捕获的选择(内部管理)。</APIItem>
    <APIItem name="mode" type="'chat' | 'insert'">控制响应是直接流式传输到文档中还是打开审查面板。默认为 `'insert'`。</APIItem>
    <APIItem name="open" type="boolean" optional>AI 菜单是否可见。默认为 `false`。</APIItem>
    <APIItem name="streaming" type="boolean" optional>响应流式传输时为 true。默认为 `false`。</APIItem>
    <APIItem name="toolName" type="'comment' | 'edit' | 'generate' | null" optional>用于解释响应的活动工具。</APIItem>
  </APIOptions>
</API>

## API

### `api.aiChat.submit(input, options?)`

向模型提供商提交提示。当省略 `mode` 时,对于折叠的光标默认为 `'insert'`,否则为 `'chat'`。

<API name="submit">
<APIParameters>
  <APIItem name="input" type="string">来自用户的原始输入。</APIItem>
  <APIItem name="options" type="object" optional>微调提交行为。</APIItem>
</APIParameters>
<APIOptions type="object">
  <APIItem name="mode" type="'chat' | 'insert'" optional>覆盖响应模式。</APIItem>
  <APIItem name="options" type="ChatRequestOptions" optional>转发到 `chat.sendMessage`(模型、标头等)。</APIItem>
  <APIItem name="prompt" type="EditorPrompt" optional>由 `getEditorPrompt` 处理的字符串、配置或函数。</APIItem>
  <APIItem name="toolName" type="'comment' | 'edit' | 'generate' | null" optional>标记提交,以便钩子可以做出不同的反应。</APIItem>
</APIOptions>
</API>

### `api.aiChat.reset(options?)`

清除聊天状态,删除 AI 节点,并可选地撤销最后一个 AI 批处理。

<API name="reset">
<APIParameters>
  <APIItem name="options" type="{ undo?: boolean }" optional>传递 `undo: false` 以保留流式内容。</APIItem>
</APIParameters>
</API>

### `api.aiChat.node(options?)`

检索与指定条件匹配的第一个 AI 节点。

<API name="node">
<APIParameters>
  <APIItem name="options" type="EditorNodesOptions &amp; { anchor?: boolean; streaming?: boolean }" optional>设置 `anchor: true` 以获取锚点节点,或 `streaming: true` 以检索当前正在流式传输到的节点。</APIItem>
</APIParameters>
<APIReturns type="NodeEntry | undefined">匹配的节点条目(如果找到)。</APIReturns>
</API>

### `api.aiChat.reload()`

使用存储的 `UseChatHelpers` 重放最后一个提示,在重新提交之前恢复原始选择或块选择。

### `api.aiChat.stop()`

停止流式传输并调用 `chat.stop`。

### `api.aiChat.show()`

打开 AI 菜单,清除先前的聊天消息,并重置工具状态。

### `api.aiChat.hide(options?)`

关闭 AI 菜单,可选地撤销最后一个 AI 批处理并重新聚焦编辑器。

<API name="hide">
<APIParameters>
  <APIItem name="options" type="{ focus?: boolean; undo?: boolean }" optional>设置 `focus: false` 以将焦点保持在编辑器外部,或 `undo: false` 以保留插入的内容。</APIItem>
</APIParameters>
</API>

## 变换

### `tf.aiChat.accept()`

接受最新响应。在插入模式下,它删除 AI 标记并将插入符号放置在流式内容的末尾。在聊天模式下,它应用待处理的建议。

### `tf.aiChat.insertBelow(sourceEditor, options?)`

在当前选择或块选择下方插入聊天预览(`sourceEditor`)。

<API name="insertBelow">
<APIParameters>
  <APIItem name="sourceEditor" type="SlateEditor">包含生成内容的编辑器。</APIItem>
  <APIItem name="options" type="{ format?: 'all' | 'none' | 'single' }" optional>从源选择复制格式。默认为 `'single'`。</APIItem>
</APIParameters>
</API>

### `tf.aiChat.replaceSelection(sourceEditor, options?)`

用聊天预览替换当前选择或块选择。

<API name="replaceSelection">
<APIParameters>
  <APIItem name="sourceEditor" type="SlateEditor">包含生成内容的编辑器。</APIItem>
  <APIItem name="options" type="{ format?: 'all' | 'none' | 'single' }" optional>控制应应用原始选择的多少格式。</APIItem>
</APIParameters>
</API>

### `tf.aiChat.removeAnchor(options?)`

删除用于定位 AI 菜单的临时锚点节点。

<API name="removeAnchor">
<APIParameters>
  <APIItem name="options" type="EditorNodesOptions" optional>过滤要删除的节点。</APIItem>
</APIParameters>
</API>

### `tf.ai.insertNodes(nodes, options?)`

在当前选择(或 `options.target`)处插入标记有 AI 标记的节点。

### `tf.ai.removeMarks(options?)`

从匹配节点中清除 AI 标记。

### `tf.ai.removeNodes(options?)`

删除标记为 AI 生成的文本节点。

### `tf.ai.undo()`

如果最新的历史条目是由 `withAIBatch` 创建的并包含 AI 内容,则撤销它。清除配对的重做条目以避免重新应用 AI 输出。

## 自定义

### 添加自定义 AI 命令

<ComponentSource name="ai-menu" />

扩展 `aiChatItems` 映射以添加新命令。每个命令都接收 `{ aiEditor, editor, input }`,并可以使用自定义提示或变换调度 `api.aiChat.submit`。

#### 简单自定义命令

```tsx
summarizeInBullets: {
  icon: <ListIcon />,
  label: 'Summarize in bullets',
  value: 'summarizeInBullets',
  onSelect: ({ editor }) => {
    void editor.getApi(AIChatPlugin).aiChat.submit('', {
      prompt: 'Summarize the current selection using bullet points',
      toolName: 'generate',
    });
  },
},
```

#### 具有复杂逻辑的命令

```tsx
generateTOC: {
  icon: <BookIcon />,
  label: 'Generate table of contents',
  value: 'generateTOC',
  onSelect: ({ editor }) => {
    const headings = editor.api.nodes({
      match: (n) => ['h1', 'h2', 'h3'].includes(n.type as string),
    });

    const prompt =
      headings.length === 0
        ? 'Create a realistic table of contents for this document'
        : 'Generate a table of contents that reflects the existing headings';

    void editor.getApi(AIChatPlugin).aiChat.submit('', {
      mode: 'insert',
      prompt,
      toolName: 'generate',
    });
  },
},
```

菜单会自动在命令和建议状态之间切换:

- `cursorCommand`:光标已折叠且尚无响应。
- `selectionCommand`:已选择文本且尚无响应。
- `cursorSuggestion` / `selectionSuggestion`:存在响应,因此会显示接受、重试或在下方插入等操作。

使用 `toolName`(`'generate' | 'edit' | 'comment'`)来控制流式钩子如何处理响应。例如,`'edit'` 启用基于差异的建议,而 `'comment'` 允许你使用 `aiCommentToRange` 将流式评论转换为讨论线程。
